/**
 * MCP Alarm Clock Server - JavaScript Implementation
 * Provides alarm clock functionality with second precision and audio playback via MCP protocol
 * Version: 0.2.0 - Using @modelcontextprotocol/sdk
 */

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { spawn } from 'child_process';
import { join, dirname, resolve } from 'path';
import { fileURLToPath } from 'url';

// Simple logger utility
const logger = {
	info: (message) => {
		const timestamp = new Date().toISOString();
		console.error(`${timestamp} - AlarmClock - INFO - ${message}`);
	},
	error: (message) => {
		const timestamp = new Date().toISOString();
		console.error(`${timestamp} - AlarmClock - ERROR - ${message}`);
	},
	debug: (message) => {
		const timestamp = new Date().toISOString();
		console.error(`${timestamp} - AlarmClock - DEBUG - ${message}`);
	},
};

// Get current directory
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

// Create MCP server instance using the official SDK
const server = new McpServer({
	name: "AlarmClock",
	version: "0.2.0",
});

// Store active alarms
const activeAlarms = new Map();
let alarmIdCounter = 1;

// Function to play alarm sound using mpv
function playAlarmSound() {
	try {
		const musicPath = join(__dirname, '../music', 'AlarmClock.mp3');
		logger.info(`Attempting to play alarm sound: ${musicPath}`);
		
		const command = 'mpv';
		const args = ['--no-video', musicPath];
		
		const mpvProcess = spawn(command, args);
		
		mpvProcess.stdout.on('data', (data) => {
			logger.debug(`mpv stdout: ${data}`);
		});
		
		mpvProcess.stderr.on('data', (data) => {
			logger.debug(`mpv stderr: ${data}`);
		});
		
		mpvProcess.on('close', (code) => {
			if (code === 0) {
				logger.info('Alarm sound played successfully');
			} else {
				logger.error(`mpv process exited with code ${code}`);
			}
		});
		
		mpvProcess.on('error', (error) => {
			logger.error(`Failed to play alarm sound: ${error.message}`);
		});
		
		return mpvProcess;
	} catch (error) {
		logger.error(`Error in playAlarmSound: ${error.message}`);
		return null;
	}
}

// Register alarm tools using the SDK

/**
 * Set a new alarm with second precision and audio playback
 */
server.tool(
	"set_alarm",
	"Set a new alarm at specified time with a message. Supports second precision and plays alarm sound when triggered.",
	{
		time: z.string().describe("Alarm time in HH:MM:SS format (24-hour) or HH:MM (seconds default to 00) or relative time like 'in 30 seconds', 'in 5 minutes'"),
		message: z.string().describe("Alarm message/description"),
	},
	async ({ time, message }) => {
		try {
			let alarmTime;
			
			// Parse time input
			if (time.startsWith('in ')) {
				// Relative time (e.g., "in 30 seconds", "in 5 minutes", "in 1 hour")
				const now = new Date();
				const parts = time.split(' ');
				if (parts.length < 3) {
					throw new Error('Invalid relative time format. Use: "in X seconds/minutes/hours"');
				}
				
				const amount = parseInt(parts[1]);
				const unit = parts[2].toLowerCase();
				
				if (isNaN(amount)) {
					throw new Error('Invalid time amount');
				}
				
				switch(unit) {
					case 'second':
					case 'seconds':
						alarmTime = new Date(now.getTime() + amount * 1000);
						break;
					case 'minute':
					case 'minutes':
						alarmTime = new Date(now.getTime() + amount * 60000);
						break;
					case 'hour':
					case 'hours':
						alarmTime = new Date(now.getTime() + amount * 3600000);
						break;
					default:
						throw new Error('Unsupported time unit. Use seconds, minutes or hours');
				}
			} else {
				// Absolute time in HH:MM:SS or HH:MM format
				const timeParts = time.split(':');
				let hours, minutes, seconds = 0;
				
				if (timeParts.length === 2) {
					// HH:MM format
					[hours, minutes] = timeParts.map(Number);
				} else if (timeParts.length === 3) {
					// HH:MM:SS format
					[hours, minutes, seconds] = timeParts.map(Number);
				} else {
					throw new Error('Invalid time format. Use HH:MM:SS or HH:MM (24-hour format)');
				}
				
				if (isNaN(hours) || isNaN(minutes) || isNaN(seconds) || 
					hours < 0 || hours > 23 || 
					minutes < 0 || minutes > 59 || 
					seconds < 0 || seconds > 59) {
					throw new Error('Invalid time format. Use HH:MM:SS or HH:MM (24-hour format) with valid ranges');
				}
				
				alarmTime = new Date();
				alarmTime.setHours(hours, minutes, seconds, 0);
				
				// If the time has already passed today, set for tomorrow
				if (alarmTime < new Date()) {
					alarmTime.setDate(alarmTime.getDate() + 1);
				}
			}
			
			const alarmId = alarmIdCounter++;
			const timeoutId = setTimeout(() => {
				logger.info(`ALARM TRIGGERED: ${message}`);
				
				// Play alarm sound
				const mpvProcess = playAlarmSound();
				
				// Store the process in alarm data for potential control
				const alarm = activeAlarms.get(alarmId);
				if (alarm) {
					alarm.mpvProcess = mpvProcess;
				}
				
				activeAlarms.delete(alarmId);
			}, alarmTime.getTime() - Date.now());
			
			activeAlarms.set(alarmId, {
				id: alarmId,
				time: alarmTime,
				message,
				timeoutId
			});
			
			logger.info(`Alarm set: ID ${alarmId} for ${alarmTime.toLocaleString()} - "${message}"`);
			
			return {
				content: [
					{
						type: "text",
						text: JSON.stringify({
							success: true,
							alarmId: alarmId,
							alarmTime: alarmTime.toISOString(),
							alarmTimeFormatted: alarmTime.toLocaleTimeString(),
							message: message,
							status: "set",
							audio: "AlarmClock.mp3 will play when triggered"
						}),
					},
				],
			};
		} catch (error) {
			logger.error(`Failed to set alarm: ${error.message}`);
			return {
				content: [
					{
						type: "text",
						text: JSON.stringify({
							success: false,
							error: error.message,
						}),
					},
				],
				isError: true,
			};
		}
	}
);

/**
 * Set a precise alarm with date and time and audio playback
 */
server.tool(
	"set_precise_alarm",
	"Set a precise alarm with specific date and time including seconds. Plays alarm sound when triggered.",
	{
		datetime: z.string().describe("Alarm date and time in ISO format (YYYY-MM-DDTHH:MM:SS) or similar parseable format"),
		message: z.string().describe("Alarm message/description"),
	},
	async ({ datetime, message }) => {
		try {
			const alarmTime = new Date(datetime);
			
			if (isNaN(alarmTime.getTime())) {
				throw new Error('Invalid datetime format. Use ISO format (YYYY-MM-DDTHH:MM:SS) or similar');
			}
			
			if (alarmTime <= new Date()) {
				throw new Error('Alarm time must be in the future');
			}
			
			const alarmId = alarmIdCounter++;
			const timeoutId = setTimeout(() => {
				logger.info(`PRECISE ALARM TRIGGERED: ${message}`);
				
				// Play alarm sound
				const mpvProcess = playAlarmSound();
				
				// Store the process in alarm data for potential control
				const alarm = activeAlarms.get(alarmId);
				if (alarm) {
					alarm.mpvProcess = mpvProcess;
				}
				
				activeAlarms.delete(alarmId);
			}, alarmTime.getTime() - Date.now());
			
			activeAlarms.set(alarmId, {
				id: alarmId,
				time: alarmTime,
				message,
				timeoutId,
				type: "precise"
			});
			
			logger.info(`Precise alarm set: ID ${alarmId} for ${alarmTime.toISOString()} - "${message}"`);
			
			return {
				content: [
					{
						type: "text",
						text: JSON.stringify({
							success: true,
							alarmId: alarmId,
							alarmTime: alarmTime.toISOString(),
							alarmTimeFormatted: alarmTime.toLocaleString(),
							message: message,
							status: "set",
							audio: "AlarmClock.mp3 will play when triggered"
						}),
					},
				],
			};
		} catch (error) {
			logger.error(`Failed to set precise alarm: ${error.message}`);
			return {
				content: [
					{
						type: "text",
						text: JSON.stringify({
							success: false,
							error: error.message,
						}),
					},
				],
				isError: true,
			};
		}
	}
);

/**
 * Test alarm sound
 */
server.tool(
	"test_alarm_sound",
	"Test the alarm sound immediately to verify it works",
	{},
	async () => {
		try {
			logger.info("Testing alarm sound");
			const mpvProcess = playAlarmSound();
			
			return {
				content: [
					{
						type: "text",
						text: JSON.stringify({
							success: true,
							message: "Alarm sound test initiated",
							audio: "Playing AlarmClock.mp3"
						}),
					},
				],
			};
		} catch (error) {
			logger.error(`Failed to test alarm sound: ${error.message}`);
			return {
				content: [
					{
						type: "text",
						text: JSON.stringify({
							success: false,
							error: error.message,
						}),
					},
				],
				isError: true,
			};
		}
	}
);

/**
 * Stop all alarm sounds
 */
server.tool(
	"stop_alarm_sounds",
	"Stop all currently playing alarm sounds",
	{},
	async () => {
		try {
			let stoppedCount = 0;
			
			// Iterate through all alarms and kill any active mpv processes
			for (const [alarmId, alarm] of activeAlarms.entries()) {
				if (alarm.mpvProcess) {
					alarm.mpvProcess.kill();
					alarm.mpvProcess = null;
					stoppedCount++;
					logger.info(`Stopped alarm sound for alarm ID: ${alarmId}`);
				}
			}
			
			logger.info(`Stopped ${stoppedCount} alarm sounds`);
			
			return {
				content: [
					{
						type: "text",
						text: JSON.stringify({
							success: true,
							stoppedCount: stoppedCount,
							message: `Stopped ${stoppedCount} alarm sounds`
						}),
					},
				],
			};
		} catch (error) {
			logger.error(`Failed to stop alarm sounds: ${error.message}`);
			return {
				content: [
					{
						type: "text",
						text: JSON.stringify({
							success: false,
							error: error.message,
						}),
					},
				],
				isError: true,
			};
		}
	}
);

/**
 * List all active alarms with second precision
 */
server.tool(
	"list_alarms",
	"List all currently active alarms with precise timing information",
	{},
	async () => {
		try {
			const now = new Date();
			const alarms = Array.from(activeAlarms.values()).map(alarm => {
				const timeRemaining = Math.max(0, alarm.time.getTime() - now.getTime());
				const secondsRemaining = Math.floor(timeRemaining / 1000);
				const minutesRemaining = Math.floor(secondsRemaining / 60);
				const hoursRemaining = Math.floor(minutesRemaining / 60);
				
				return {
					id: alarm.id,
					time: alarm.time.toISOString(),
					timeFormatted: alarm.time.toLocaleString(),
					message: alarm.message,
					timeRemainingMs: timeRemaining,
					timeRemaining: {
						hours: hoursRemaining,
						minutes: minutesRemaining % 60,
						seconds: secondsRemaining % 60,
						totalSeconds: secondsRemaining
					},
					type: alarm.type || "time",
					hasAudio: true
				};
			});
			
			logger.info(`Listed ${alarms.length} active alarms`);
			
			return {
				content: [
					{
						type: "text",
						text: JSON.stringify({
							success: true,
							alarms: alarms,
							count: alarms.length
						}),
					},
				],
			};
		} catch (error) {
			logger.error(`Failed to list alarms: ${error.message}`);
			return {
				content: [
					{
						type: "text",
						text: JSON.stringify({
							success: false,
							error: error.message,
						}),
					},
				],
				isError: true,
			};
		}
	}
);

/**
 * Cancel an alarm
 */
server.tool(
	"cancel_alarm",
	"Cancel a specific alarm by ID",
	{
		alarmId: z.number().describe("ID of the alarm to cancel"),
	},
	async ({ alarmId }) => {
		try {
			const alarm = activeAlarms.get(alarmId);
			if (!alarm) {
				throw new Error(`Alarm with ID ${alarmId} not found`);
			}
			
			clearTimeout(alarm.timeoutId);
			
			// Also stop any playing audio for this alarm
			if (alarm.mpvProcess) {
				alarm.mpvProcess.kill();
			}
			
			activeAlarms.delete(alarmId);
			
			logger.info(`Alarm cancelled: ID ${alarmId} - "${alarm.message}"`);
			
			return {
				content: [
					{
						type: "text",
						text: JSON.stringify({
							success: true,
							alarmId: alarmId,
							message: "Alarm cancelled successfully"
						}),
					},
				],
			};
		} catch (error) {
			logger.error(`Failed to cancel alarm: ${error.message}`);
			return {
				content: [
					{
						type: "text",
						text: JSON.stringify({
							success: false,
							error: error.message,
						}),
					},
				],
				isError: true,
			};
		}
	}
);

/**
 * Get current time with millisecond precision
 */
server.tool(
	"get_time",
	"Get the current date and time with millisecond precision",
	{},
	async () => {
		try {
			const now = new Date();
			logger.info(`Current time requested: ${now.toISOString()}`);
			
			return {
				content: [
					{
						type: "text",
						text: JSON.stringify({
							success: true,
							currentTime: now.toISOString(),
							localTime: now.toLocaleString(),
							timestamp: now.getTime(),
							utc: now.toUTCString(),
							components: {
								year: now.getFullYear(),
								month: now.getMonth() + 1,
								day: now.getDate(),
								hours: now.getHours(),
								minutes: now.getMinutes(),
								seconds: now.getSeconds(),
								milliseconds: now.getMilliseconds()
							}
						}),
					},
				],
			};
		} catch (error) {
			logger.error(`Failed to get time: ${error.message}`);
			return {
				content: [
					{
						type: "text",
						text: JSON.stringify({
							success: false,
							error: error.message,
						}),
					},
				],
				isError: true,
			};
		}
	}
);

// Start the server if this file is run directly
async function main() {
	logger.info("Starting MCP Alarm Clock server with audio support");

	const transport = new StdioServerTransport();
	await server.connect(transport);

	logger.info("MCP Alarm Clock server is running on stdio");
}

// Run the server if this file is executed directly
const currentFile = fileURLToPath(import.meta.url);
const argFile = process.argv[1] ? resolve(process.argv[1]) : null;

if (argFile && currentFile === argFile) {
	main().catch((error) => {
		logger.error(`Failed to start server: ${error.message}`);
		process.exit(1);
	});
}

export default server;